package world;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * Class representing the hex grid spaces in a CritterWorld
 */
public class Hex {

	private boolean isRock; //if this Hex is a rock, it contains nothing else
	private boolean hasPlant;
	private boolean newPlant; //became a plant Hex this timestep
	private int foodContained;
	private Critter critter; //null if this Hex has no Critter
	private final Location location;
	private CritterWorld world;
	
	// This field is used only for critter sensing. It marks the distance from
	// the critter doing the sensing, and it gets reset to -1 after every sense.
	public int distance = -1;
	
	/**
	 * Creates an empty Hex with the given coordinates
	 */
	public Hex(CritterWorld w, Location loc){
		if (!loc.isValid()) 
			throw new IllegalArgumentException("Hexes can only be created on valid Locations");
		location = loc;
		world = w;
	}
	
	public Location getLocation() {
		return location;
	}
	
	public int getFood(){
		return foodContained;
	}
	
	public int getEnergy(){
		if(hasPlant)
			return foodContained + world.ENERGY_PER_PLANT;
		else
			return foodContained;
	}
	
	public boolean hasPlant(){
		return hasPlant;
	}
	
	public boolean isRock(){
		return isRock;
	}
	
	public boolean hasCritter(){
		return critter != null;
	}
	
	/**
	 * Returns false if a Critter can move to this Hex
	 */
	public boolean isOccupied(){
		return isRock || hasCritter();
	}
	
	/**
	 * Returns the Critter contained by this Hex. If this Hex has no
	 * Critter, returns null.
	 */
	public Critter getCritter(){
		return critter;
	}
	
	/**
	 * Puts newCritter into this Hex, unless this Hex already has a Critter
	 * or is a rock.
	 * @return false if this Hex already has a Critter, or is a rock
	 */
	public boolean putCritter(Critter newCritter){
		synchronized(world){
			if (isRock || critter != null)
				return false;
			critter = newCritter;
			return true;
		}
	}
	
	/**
	 * Removes the critter that this Hex contains
	 * @return true if a change was made
	 */
	public boolean removeCritter(){
		synchronized(world){
			boolean rtn = critter != null;
			critter = null;
			return rtn;
		}
	}
	
	/**
	 * Makes this Hex into a rock
	 * Checks: this Hex is empty.
	 * @return true if a change was made
	 */
	public boolean makeRock(){
		synchronized(world){
			if (isRock || critter != null)
				return false;
			isRock = true;
			world.incrementRocks();
			hasPlant = false;
			foodContained = 0;
			return true;
		}
	}

	/**
	 * Gives this Hex a plant
	 * @return true if a change was made
	 */
	public boolean putPlant(){
		synchronized(world){
			if(hasPlant || isRock)
				return false;
			hasPlant = true;
			newPlant = true;
			world.putPlantHex(this);
			return true;
		}
	}

	/**
	 * Makes one new plant randomly in one of the neighboring hexes, if possible
	 * @return true if a plant was sprouted
	 */
	public boolean sproutPlant(){
		boolean sprouted = false;
		if(!newPlant){
			Random rnd = new Random();
			int dir = rnd.nextInt(6);
			int tries = 0;
			Hex garden;
			do{
				garden = world.getHex(location.nextLocation(dir));
				if(!(garden == null || garden.isRock() || garden.hasPlant())){
					garden.putPlant();
					sprouted = true;
				} else{
					dir = (dir + 1)%6;
					tries++;
				}
			} while(!sprouted && tries < 6);
		}
		return sprouted;
	}
	
	/**
	 * Removes the plant from this Hex if there was a plant
	 * @return the food value of plant eaten.
	 */
	public int plantEaten(){
		if(!hasPlant)
			return 0;
		world.removePlantHex(this);
		hasPlant = false;
		return world.ENERGY_PER_PLANT;
	}
	
	/**
	 * Removes any food from this Hex and returns how much food it contained
	 * @return previously contained food
	 */
	public int foodEaten(){
		int food = foodContained;
		foodContained = 0;
		return food;
	}
	
	/**
	 * Adds the given amount of food to this Hex if this Hex is not a rock.
	 * @param foodAdded the amount of food to be added.
	 * @return true if a change was made.
	 */
	public boolean putFood(int foodAdded) {
		synchronized(world){
			if (isRock) return false;
			foodContained += foodAdded;
			return true;
		}
	}
	
	public void getReady(){
		newPlant = false;
	}
	
	/**
	 * Returns a list of adjacent hexes that are not rocks.
	 */
	public List<Hex> getReachableHexes() {
		List<Hex> rtn = new LinkedList<Hex>();
		Hex up = world.getHex(location.nextLocation(0));
		if (up != null && !up.isRock) rtn.add(up);
		Hex upRight = world.getHex(location.nextLocation(1));
		if (upRight != null && !upRight.isRock) rtn.add(upRight);
		Hex downRight = world.getHex(location.nextLocation(2));
		if (downRight != null && !downRight.isRock) rtn.add(downRight);
		Hex down = world.getHex(location.nextLocation(3));
		if (down != null && !down.isRock) rtn.add(down);
		Hex downLeft = world.getHex(location.nextLocation(4));
		if (downLeft != null && !downLeft.isRock) rtn.add(downLeft);
		Hex upLeft = world.getHex(location.nextLocation(5));
		if (upLeft != null && !upLeft.isRock) rtn.add(upLeft);
		return rtn;
	}
}
